/*
 * Copyright (c) 2010, Jim Hollinger
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Jim Hollinger nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <string>
#include <string.h>

#include "debughelper/debughelper.h"
#include "logger/logger.h"
#include "datachannel/filechannel.h"

#if defined (_DARWIN)
size_t strnlen(const char *s, size_t max) {
    register const char *p;
    for(p = s; *p && max--; ++p);
    return(p - s);
}
#endif

using namespace datachannel;


/**
 ****************************************************************************
 * @brief  Constructor
 ****************************************************************************
**/
FileChannel::FileChannel(logger::Logger *logger)
    : DataChannel(logger), fstream(NULL) {
    mode = READ;
    type = BINARY;
}

/**
 ****************************************************************************
 * @brief  Destructor
 ****************************************************************************
**/
FileChannel::~FileChannel() {
    if (fstream != NULL) {
        fclose(fstream);
        fstream = NULL;
    }
}

/**
 ****************************************************************************
 * @brief  Opens the file.
 *
 * @param  descriptor  Filename to open
 *                      filename[:read|:write][:binary|:text]
 *                      default = read + binary
 ****************************************************************************
**/
bool FileChannel::open(const std::string &descriptor) {
    if (fstream != NULL) {
        close();
    }
    size_t i = descriptor.find_first_of(':');
    if (i == std::string::npos) {
        filename = descriptor;
    } else {
        filename = descriptor.substr(0, i);
        if (descriptor.find(":read") != std::string::npos) {
            setFileAccessMode(READ);
        }
        if (descriptor.find(":write") != std::string::npos) {
            setFileAccessMode(WRITE);
        }
        if (descriptor.find(":binary") != std::string::npos) {
            setFileContentType(BINARY);
        }
        if (descriptor.find(":text") != std::string::npos) {
            setFileContentType(TEXT);
        }
    }
    if (mode == READ) {
        if (type == BINARY) {
            fstream = fopen(filename.c_str(), "rb");
        } else {  // TEXT
            fstream = fopen(filename.c_str(), "rt");
        }
        if (fstream == NULL) {
            log->error("FileChannel: Unable to open \"%s\" for reading",
                filename.c_str());
        } else {
            log->info("FileChannel opened \"%s\" for reading",
                filename.c_str());
        }
    } else {  // WRITE
        if (type == BINARY) {
            fstream = fopen(filename.c_str(), "wb");
        } else {  // TEXT
            fstream = fopen(filename.c_str(), "wt");
        }
        if (fstream == NULL) {
            log->error("FileChannel: Unable to open \"%s\" for writing",
                filename.c_str());
        } else {
            log->info("FileChannel opened \"%s\" for writing",
                filename.c_str());
        }
    }

    return (fstream != NULL);
}

/**
 ****************************************************************************
 * @brief  Closes the file.
 ****************************************************************************
**/
bool FileChannel::close() {
    if (fstream != NULL) {
        close();
        fstream = NULL;
    }

    return (fstream == NULL);
}

/**
 ****************************************************************************
 * @brief  Reads a line from the file.
 ****************************************************************************
**/
int FileChannel::read(void *buffer, int maxCount) {
    int numRead = 0;

    if (fstream == NULL) {
        log->warn("FileChannel.read: \"%s\" not opened", filename.c_str());
    } else {
        if (mode == READ) {
            char *s = fgets(static_cast<char *> (buffer), maxCount, fstream);
            if (s != NULL) {
                numRead = strnlen(s, maxCount);
            }
        } else {
            log->warn("FileChannel.read: \"%s\" not opened for reading",
                filename.c_str());
        }
    }

    return numRead;
}

/**
 ****************************************************************************
 * @brief  Writes to the file.
 ****************************************************************************
**/
int FileChannel::write(const void *buffer, int count) {
    int numWritten = 0;

    if (fstream == NULL) {
        log->warn("FileChannel.read: \"%s\" not opened", filename.c_str());
    } else {
        if (mode == WRITE) {
            numWritten = fwrite(buffer, 1, count, fstream);
        } else {
            log->warn("FileChannel.write: \"%s\" not opened for writing",
                filename.c_str());
        }
    }

    return numWritten;
}

/**
 ****************************************************************************
 * @brief  Sets the file access mode.
 *
 * @param  mode  File access mode (read or write)
 ****************************************************************************
**/
void FileChannel::setFileAccessMode(FileAccessMode new_mode) {
    mode = new_mode;
}

/**
 ****************************************************************************
 * @brief  Sets the file content type.
 *
 * @param  type  File content type (binary or text)
 ****************************************************************************
**/
void FileChannel::setFileContentType(FileContentType new_type) {
    type = new_type;
}
